package com.cyy.common.service.impl;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.http.useragent.UserAgent;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cyy.common.constant.CacheKey;
import com.cyy.common.constant.Constants;
import com.cyy.common.constant.MenuType;
import com.cyy.common.domain.BaseEntity;
import com.cyy.common.exception.ProcessException;
import com.cyy.common.mapper.*;
import com.cyy.common.pojo.dto.MetaDto;
import com.cyy.common.pojo.dto.RouterDto;
import com.cyy.common.pojo.dto.UserFormDto;
import com.cyy.common.pojo.entity.*;
import com.cyy.common.pojo.query.system.SystemUserQuery;
import com.cyy.common.pojo.req.LoginReq;
import com.cyy.common.pojo.req.RegisterReq;
import com.cyy.common.pojo.resp.UserInfoResp;
import com.cyy.common.pojo.vo.system.SystemUserVo;
import com.cyy.common.service.UserService;
import com.cyy.common.utils.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author 曹云友
 * @since 2024-03-27
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {


    private final RedisUtils redisUtils;
    private final UserRoleMapper userRoleMapper;
    private final RoleMapper roleMapper;
    private final RoleMenuMapper roleMenuMapper;
    private final MenuMapper menuMapper;


    @Override
    public String login(LoginReq loginReq) {


        String cacheKey = String.join(Constants.COLON, CacheKey.CAPTCHA_LOGIN, loginReq.getCaptchaKey());
        // 验证码校验
        String code = Optional.ofNullable(
                (String) redisUtils.get(cacheKey)
        ).orElseThrow(() -> new ProcessException("验证码错误!"));

        if (!code.equalsIgnoreCase(loginReq.getCaptcha())) {
            throw new ProcessException("验证码错误!");
        }

        // 删除对应缓存
        redisUtils.del(cacheKey);

        User user = Optional.ofNullable(baseMapper.selectOne(Wrappers.<User>lambdaQuery()
                .eq(User::getUsername, loginReq.getUsername())
                .eq(User::getPassword, EncryptUtils.aesEncrypt(loginReq.getPassword())))
        ).orElseThrow(() -> new ProcessException("账号或者密码错误"));


        // 用户信息设置

        HttpServletRequest request = RequestContextUtils.getRequest();
        UserAgent parse = IpUtils.getUserAgent(request);

        SaLoginModel loginModel = new SaLoginModel();
        loginModel.setDevice(parse.getPlatform().getName());
        loginModel.setIsLastingCookie(true);

        if (loginReq.getRememberMe()) {
            StpUtil.login(user.getId(), loginModel.setTimeout(60 * 60 * 24 * 7));
        } else {
            StpUtil.login(user.getId(), loginModel);
        }

        String tokenValue = StpUtil.getTokenValue();
        log.info("token:{}", tokenValue);
        return tokenValue;
    }

    @Override
    public String getVerifyCode(String key) {

        try {
            CaptchaUtils.CodeEntity codeEntity = CaptchaUtils.generateCode();
            String code = codeEntity.getCode();
            redisUtils.set(String.join(Constants.COLON, CacheKey.CAPTCHA_LOGIN, key), code, 300);
            return codeEntity.getImg();
        } catch (IOException e) {
            throw new ProcessException("验证码生成失败!");
        }

    }

    @Override
    public void register(RegisterReq req) {
        // 校验是否存在对应客户信息
        Assert.isFalse(baseMapper.exists(Wrappers.<User>lambdaQuery().eq(User::getUsername, req.getUsername())),
                "已存在对应用户，请修改账户后重试！");

        // 密码确认校验
        Assert.isFalse(req.getCheckPwd().equals(req.getPassword()), "请确认两次输入的密码是否一致！");
        // 格式校验
        Pattern compile = Pattern.compile(Constants.PWD_REGEXP);
        Assert.isFalse(compile.matcher(req.getPassword()).matches(), "密码格式错误，请确认密码格式后重新提交！");

        // 默认信息设置
        User user = new User();
        user.setUsername(req.getUsername());
        user.setPassword(req.getPassword());
        user.setNickname("随机名称");
        user.setEmail(req.getEmail());

        // 基础角色设置

        baseMapper.insert(user);

    }

    @Override
    public UserInfoResp getUserInfo() {
        User user = baseMapper.selectById(StpUtil.getLoginIdAsString());
        UserInfoResp resp = new UserInfoResp();
        BeanUtils.copyProperties(user, resp);
        resp.setUserId(user.getId());
        resp.setNickname(user.getNickname());
        resp.setPerms(StpUtil.getPermissionList());
        resp.setRoles(StpUtil.getRoleList());
        return resp;
    }

    @Override
    public List<RouterDto> getUserMenus() {

        // 获取路由信息
        List<UserRole> userRoles = userRoleMapper.selectList(Wrappers.<UserRole>lambdaQuery()
                .select(UserRole::getRoleId)
                .eq(UserRole::getUserId, StpUtil.getLoginIdAsString()));
        if (CollectionUtil.isEmpty(userRoles)) {
            throw new ProcessException("该用户角色信息有误，未查询到该用户角色信息");
        }

        List<Integer> roleIds = userRoles.stream().map(UserRole::getRoleId).toList();

        List<RoleMenu> roleMenus = roleMenuMapper.selectList(Wrappers.<RoleMenu>lambdaQuery()
                .select(RoleMenu::getMenuId)
                .in(RoleMenu::getRoleId, roleIds)
        );
        if (CollectionUtil.isEmpty(roleMenus)) {
            throw new ProcessException("该用户菜单资源为空，请配置菜单资源后重试");
        }
        List<Integer> menuIds = roleMenus.stream().map(RoleMenu::getMenuId).toList();

        List<Menu> menus = menuMapper.selectList(Wrappers.<Menu>lambdaQuery()
                .in(Menu::getMenuType, MenuType.MENU, MenuType.CATALOG, MenuType.EXTLINK)
                .in(Menu::getId, menuIds)
                .eq(Menu::getStatus, Boolean.TRUE)
                .orderByDesc(Menu::getMenuSort)
        );
        if (CollectionUtil.isEmpty(menus)) {
            throw new ProcessException("该用户菜单资源为空，请配置菜单资源后重试");
        }
        //?
        List<RouterDto> list = menus.stream().map(m -> {
            RouterDto resp = new RouterDto();
            resp.setId(m.getId());
            resp.setParentId(m.getParentId());
            resp.setPath(m.getPath());
            resp.setComponent(m.getComponent());
            resp.setRedirect(m.getRedirect());
            resp.setName(m.getMenuName());
            resp.setMeta(new MetaDto()
                    .setTitle(m.getMenuName())
                    .setIcon(m.getIcon())
                    .setHidden(m.getHidden())
                    .setKeepAlive(m.getKeepAlive())
                    .setAlwaysShow(m.getAlwaysShow())
            );
            return resp;
        }).collect(Collectors.toList());

        // 哨兵节点
        RouterDto preNode = new RouterDto();
        preNode.setId(0);
        HashSet<Integer> set = new HashSet<>();
        TreeUtils.generatorTree(list, preNode, set);
        // 清理垃圾
        set.clear();
        list.clear();


        return preNode.getChildren();
    }

    @Override
    public IPage<SystemUserVo> getPage(SystemUserQuery query) {
        return baseMapper.getPage(PageUtils.getPage(), query);
    }

    @Override
    public UserFormDto getUserForm(Integer userId) {
        User user = Optional.ofNullable(baseMapper.selectById(userId))
                .orElseThrow(() -> new ProcessException("未找到对应用户信息"));

        UserFormDto formDto = new UserFormDto();
        BeanUtils.copyProperties(user, formDto);

        // 角色
        List<UserRole> userRoles = userRoleMapper.selectList(Wrappers.<UserRole>lambdaQuery()
                .select(UserRole::getRoleId)
                .eq(UserRole::getUserId, userId)
        );
        if (CollectionUtil.isNotEmpty(userRoles)) {
            formDto.setRoleIds(userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toList()));
        }

        return formDto;
    }

    @Override
    public void addUser(UserFormDto req) {
        // 参数判断
        Assert.isFalse(
                StringUtils.isAnyBlank(req.getUsername(), req.getNickname())
                        || CollectionUtil.isEmpty(req.getRoleIds())
                , "参数错误！");
        // 判断是否由相同信息的用户存在
        boolean exists = baseMapper.exists(Wrappers.<User>lambdaQuery()
                .eq(User::getUsername, req.getUsername()));
        if (exists){
            throw new ProcessException("已存在对应用户，请修改账户名后重试！");
        }


        User user = new User();
        BeanUtils.copyProperties(req, user);
        user.setId(null);

        // 如果传入的密码为空 设置默认密码 123456
        if (StringUtils.isBlank(user.getPassword())){
            user.setPassword(EncryptUtils.aesEncrypt("123456"));
        }

        this.save(user);

        // 用户角色处理:选出其中存在的角色信息，并赋予该用户
        this.insertUserRoles(req.getRoleIds(), user.getId());

    }

    @Override
    public void updateUser(UserFormDto req) {

        // 参数判断
        Assert.isFalse(
                StringUtils.isAnyBlank(req.getUsername(), req.getNickname())
                        || CollectionUtil.isEmpty(req.getRoleIds())
                , "参数错误！");

        User user = new User();
        BeanUtils.copyProperties(req, user);

        baseMapper.updateById(user);

        // 还是先删再插，省事。但是需要过滤出有用的 roleId
        userRoleMapper.delete(Wrappers.<UserRole>lambdaQuery().eq(UserRole::getUserId, req.getId()));

        this.insertUserRoles(req.getRoleIds(), user.getId());


    }

    @Override
    public void updateUserPassword(Integer userId, String password) {
        // 查询是否存在对应用户

        User user = Optional.ofNullable(getOne(Wrappers.<User>lambdaQuery().eq(BaseEntity::getId, userId)))
                .orElseThrow(() -> new ProcessException("未找到对应的用户信息"));

        user.setPassword(EncryptUtils.aesEncrypt(password));

        baseMapper.updateById(user);

    }

    @Override
    public void deleteUsers(String ids) {
        List<String> idList = Arrays.asList(ids.split(Constants.COMMA));

        baseMapper.update(Wrappers.<User>lambdaUpdate().set(BaseEntity::getStatus, Boolean.FALSE)
                .in(BaseEntity::getId, idList));
    }

    /**
     * 保存用户角色信息
     *
     * @param roleIds
     * @param userId
     */
    private void insertUserRoles(List<Integer> roleIds, Integer userId) {
        List<Role> roles = roleMapper.selectList(Wrappers.<Role>lambdaQuery()
                .select(BaseEntity::getId)
                .in(BaseEntity::getId, roleIds));
        if (CollectionUtil.isEmpty(roles)) {
            return;
        }
        // 事实存在的角色ID
        List<Integer> existsRoleId = roles.stream().map(BaseEntity::getId).collect(Collectors.toList());

        // 插入用户角色信息
        userRoleMapper.insertUserRoles(userId, existsRoleId);
    }
}
